#coding:utf-8
import logging
import logging.handlers
import os
import time
import sys
import socket
from logging.handlers import TimedRotatingFileHandler
import multiprocessing
import random
try:
    import codecs
except ImportError:
    codecs = None


def initTimedLogger(log_file_path,name=None,filter=None,file_nums=10,need_error_log=False,level=None,log_fmt=None):
    '''
    不传入名字的话，默认的初始化的是RootLogger
    传入名字的话，初始化为指定名字的logger
    '''
    logger = logging.getLogger(name)
    hdlr = MPRotatingFileHandler(log_file_path,'midnight',1,file_nums)
    if not log_fmt:
        log_fmt = '%(asctime)s %(name)s %(levelname)s|%(process)d %(message)s'
    formatter = logging.Formatter(log_fmt)
    hdlr.setFormatter(formatter)
    if filter:
        filter=logging.Filter(filter)
        logger.addFilter(filter)
    logger.addHandler(hdlr)
    if need_error_log:
        errHandler = MPRotatingFileHandler('%s.err'%log_file_path,'midnight',1,file_nums)
        errHandler.setFormatter(formatter)
        errHandler.setLevel(logging.ERROR)
        logger.addHandler(errHandler)
    if level:
        logger.setLevel(level)
    else:
        logger.setLevel(logging.INFO)
    return logger


def initRotatingLogger(log_file_path,name=None,filter=None,multilog=False):
    '''
    不传入名字的话，默认的初始化的是RootLogger
    传入名字的话，初始化为指定名字的logger
    '''
    logger = logging.getLogger(name)
    #hdlr = logging.StreamHandler()
    #hdlr = logging.FileHandler(logfile)
    hdlr = logging.handlers.RotatingFileHandler(log_file_path,'a',102400000,5)
    formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s| %(message)s')
    hdlr.setFormatter(formatter)
    if filter:
        filter=logging.Filter(filter)
        logger.addFilter(filter)
    logger.addHandler(hdlr)
    if multilog:#error和info区分
        errHandler = logging.handlers.RotatingFileHandler('%s.err'%log_file_path,'a',102400000,5)
        errHandler.setFormatter(formatter)
        errHandler.setLevel(logging.ERROR)
        logger.addHandler(errHandler)
    logger.setLevel(logging.INFO)
    return logger


def retry_local(func):
    def _(self, *args, **kwargs):
        try:
            fname = func.func_name
            if not self.scribe_server_down:# or (self.error_times % 100000) == 1:
                func1 = getattr(self.scribe_logger,fname)
                func1(*args, **kwargs)
            else:
                if not self.local_logger:
                    self.local_logger = logging.getLogger(self.local_name)
                func2 = getattr(self.local_logger,fname)
                func2(*args, **kwargs)
                self.error_times += 1
        except socket.timeout:
            # don't care about timeout sometimes in logging scenario
            self.error_times += 1
            pass
        except Exception:
            if not self.local_logger:
                self.local_logger = logging.getLogger(self.local_name)
            self.scribe_server_down = True
            func2 = getattr(self.local_logger,fname)
            func2(*args, **kwargs)
            self.local_logger.error('scribe server down')
            self.error_times += 1
    return _

lock = multiprocessing.Lock()
class SafeRotatingFileHandler(TimedRotatingFileHandler):
    def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False):
        TimedRotatingFileHandler.__init__(self, filename, when, interval, backupCount, encoding, delay, utc)
        self.delay = delay
        self.randomRolloverAt()

    """
    随机rollover时间,防止多进程同时滚动
    """
    def randomRolloverAt(self):
        if self.when == 'MIDNIGHT' or self.when.startswith('W'):
            self.rolloverAt += random.random() * 100

    """
    重写一下TimedRotatingFileHandler，解决多进程logger错乱的问题
    """
    def doRollover(self):
        """
        do a rollover; in this case, a date/time stamp is appended to the filename
        when the rollover happens.  However, you want the file to be named for the
        start of the interval, not the current time.  If there is a backup count,
        then we have to get a list of matching filenames, sort them and remove
        the one with the oldest suffix.
        
        Override,   1. if dfn not exist then do rename
                    2. _open with "a" model
        """
        if self.stream:
            self.stream.close()
            self.stream = None
        # get the time that this sequence started at and make it a TimeTuple
        currentTime = int(time.time())
        dstNow = time.localtime(currentTime)[-1]
        t = self.rolloverAt - self.interval
        if self.utc:
            timeTuple = time.gmtime(t)
        else:
            timeTuple = time.localtime(t)
            dstThen = timeTuple[-1]
            if dstNow != dstThen:
                if dstNow:
                    addend = 3600
                else:
                    addend = -3600
                timeTuple = time.localtime(t + addend)
        dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
        global lock
        with lock:
            if not os.path.exists(dfn) and os.path.exists(self.baseFilename):
                os.rename(self.baseFilename, dfn)
        if self.backupCount > 0:
            for s in self.getFilesToDelete():
                os.remove(s)
        if not self.delay:
            self.mode = "a"
            self.stream = self._open()
        newRolloverAt = self.computeRollover(currentTime)
        while newRolloverAt <= currentTime:
            newRolloverAt = newRolloverAt + self.interval
        #If DST changes and midnight or weekly rollover, adjust for this.
        if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
            dstAtRollover = time.localtime(newRolloverAt)[-1]
            if dstNow != dstAtRollover:
                if not dstNow:  # DST kicks in before next rollover, so we need to deduct an hour
                    addend = -3600
                else:           # DST bows out before next rollover, so we need to add an hour
                    addend = 3600
                newRolloverAt += addend
        self.rolloverAt = newRolloverAt
        self.randomRolloverAt()


'''
进程安全文件handler
'''
class MPRotatingFileHandler(TimedRotatingFileHandler):
    def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False):
        if codecs is None:
            encoding = None
        TimedRotatingFileHandler.__init__(self, filename, when, interval, backupCount, encoding, True, utc)
        self.delay = delay
        if not self.delay:
            self.stream = self._open()

    def _open(self):
        """
        Open the current base file with the (original) mode and encoding.
        Return the resulting stream.
        """
        if self.utc:
            timeTuple = time.gmtime(time.time())
        else:
            timeTuple = time.localtime(time.time())
        dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
        if self.encoding is None:
            stream = open(dfn, self.mode)
        else:
            stream = codecs.open(dfn, self.mode, self.encoding)
        return stream

    def doRollover(self):
        """
        do a rollover; in this case, a date/time stamp is appended to the filename
        when the rollover happens.  However, you want the file to be named for the
        start of the interval, not the current time.  If there is a backup count,
        then we have to get a list of matching filenames, sort them and remove
        the one with the oldest suffix.
        """
        if self.stream:
            self.stream.close()
            self.stream = None
        if self.backupCount > 0:
            for s in self.getFilesToDelete():
                os.remove(s)
        if not self.delay:
            self.stream = self._open()
        currentTime = int(time.time())
        newRolloverAt = self.computeRollover(currentTime)
        while newRolloverAt <= currentTime:
            newRolloverAt = newRolloverAt + self.interval
        self.rolloverAt = newRolloverAt


def initMpTimedLogger(log_file_path,name=None,filter=None,file_nums=10,need_error_log=False,level=None,log_fmt=None):
    '''
    多进程timedlogger
    不传入名字的话，默认的初始化的是RootLogger
    传入名字的话，初始化为指定名字的logger
    '''
    logger = logging.getLogger(name)
    hdlr = SafeRotatingFileHandler(log_file_path,'midnight',1,file_nums)
    if not log_fmt:
        log_fmt = '%(asctime)s %(name)s %(levelname)s|%(process)d %(message)s'
    formatter = logging.Formatter(log_fmt)
    hdlr.setFormatter(formatter)
    if filter:
        filter=logging.Filter(filter)
        logger.addFilter(filter)
    logger.addHandler(hdlr)
    if need_error_log:
        errHandler = SafeRotatingFileHandler('%s.err'%log_file_path,'midnight',1,file_nums)
        errHandler.setFormatter(formatter)
        errHandler.setLevel(logging.ERROR)
        logger.addHandler(errHandler)
    if level:
        logger.setLevel(level)
    else:
        logger.setLevel(logging.INFO)
    return logger


def getOutputLogger(name,level=logging.DEBUG):
    '''
    返回只输出到标准输出和标准错误的logger,用来替代print语句
    必须提供name
    '''
    logger = logging.getLogger(name)
    logger.setLevel(level)
    hdr = logging.StreamHandler()
    formatter = logging.Formatter('[%(asctime)s] %(name)s:%(levelname)s: %(message)s')
    hdr.setFormatter(formatter)
    logger.addHandler(hdr)
    return logger

if __name__=='__main__':
    initTimedLogger('test.log',name='ttt')
    log = logging.getLogger('ttt')
    log.info('test info')
    log.warn('test warning')
    log.error('test error')
    log = getOutputLogger('test')
    log.info('test info')
    log.warn('test warning')
    log.error('test error')

